package zw_601_700.zw_645_错误的集合;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

class Solution {
    public static void main(String[] args) {
        int[] nums = {1, 2, 2, 4};
        int[] errorNums = findErrorNums(nums);
        for (int errorNum : errorNums) {
            System.out.println(errorNum);
        }
    }


    /**
     * 方法三：位运算
     * 使用位运算，可以达到 O(n) 的时间复杂度和 O(1) 的空间复杂度。
     * 重复的数字在数组中出现 2 次，丢失的数字在数组中出现 0 次，其余的每个数字在数组中出现 1 次。
     * 由此可见，重复的数字和丢失的数字的出现次数的奇偶性相同，且和其余的每个数字的出现次数的奇偶性不同。
     * 如果在数组的 n 个数字后面再添加从 1 到 n 的每个数字，得到 2n 个数字，则在 2n 个数字中，重复的数字出现 3 次，丢失的数字出现 1 次，其余的每个数字出现 2 次。
     * 根据出现次数的奇偶性，可以使用异或运算求解。
     *
     * @param nums
     * @return
     */
    public static int[] findErrorNums(int[] nums) {
        int n = nums.length;
        int xor = 0;
        for (int num : nums) {
            xor ^= num;
        }
        for (int i = 1; i <= n; i++) {
            xor ^= i;
        }
        int lowbit = xor & (-xor);
        int num1 = 0, num2 = 0;
        for (int num : nums) {
            if ((num & lowbit) == 0) {
                num1 ^= num;
            } else {
                num2 ^= num;
            }
        }
        for (int i = 1; i <= n; i++) {
            if ((i & lowbit) == 0) {
                num1 ^= i;
            } else {
                num2 ^= i;
            }
        }
        for (int num : nums) {
            if (num == num1) {
                return new int[]{num1, num2};
            }
        }
        return new int[]{num2, num1};
    }


    /**
     * 方法二：哈希表
     * 重复的数字在数组中出现 2 次，丢失的数字在数组中出现 0 次，其余的每个数字在数组中出现 1 次。
     * 因此可以使用哈希表记录每个元素在数组中出现的次数，然后遍历从 1 到 n 的每个数字，分别找到出现 2 次和出现 0 次的数字，即为重复的数字和丢失的数字。
     *
     * @param nums
     * @return
     */
//    public static int[] findErrorNums(int[] nums) {
//        int[] errorNums = new int[2];
//        int n = nums.length;
//        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//        for (int num : nums) {
//            map.put(num, map.getOrDefault(num, 0) + 1);
//        }
//        for (int i = 1; i <= n; i++) {
//            int count = map.getOrDefault(i, 0);
//            if (count == 2) {
//                errorNums[0] = i;
//            } else if (count == 0) {
//                errorNums[1] = i;
//            }
//        }
//        return errorNums;
//    }


    /**
     * 方法一：排序
     * 将数组排序之后，比较每对相邻的元素，即可找到错误的集合。
     * 寻找重复的数字较为简单，如果相邻的两个元素相等，则该元素为重复的数字。
     * 寻找丢失的数字相对复杂，可能有以下两种情况：
     *      如果丢失的数字大于 1 且小于 n，则一定存在相邻的两个元素的差等于 2，这两个元素之间的值即为丢失的数字；
     *      如果丢失的数字是 1 或 n，则需要另外判断。
     * 为了寻找丢失的数字，需要在遍历已排序数组的同时记录上一个元素，然后计算当前元素与上一个元素的差。考虑到丢失的数字可能是 1，因此需要将上一个元素初始化为 0。
     *      当丢失的数字小于 n 时，通过计算当前元素与上一个元素的差，即可得到丢失的数字；
     *      如果nums[n−1]不等于n，则丢失的数字是 n。
     *
     * @param nums
     * @return
     */
//    public static int[] findErrorNums(int[] nums) {
//        int[] errorNums = new int[2];
//        int n = nums.length;
//        Arrays.sort(nums);
//        int prev = 0;
//        for (int i = 0; i < n; i++) {
//            int curr = nums[i];
//            if (curr == prev) {
//                errorNums[0] = prev;
//            } else if (curr - prev > 1) {
//                errorNums[1] = prev + 1;
//            }
//            prev = curr;
//        }
//        if (nums[n - 1] != n) {
//            errorNums[1] = n;
//        }
//        return errorNums;
//    }
}
